package com.yunqian.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.yunqian.model.BaseModel;
import com.yunqian.repository.BaseRepository;
import com.yunqian.util.validate.Validator;
import com.yunqian.web.FilterCondition;
import org.hibernate.Session;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.transaction.Transactional;
import java.io.IOException;
import java.io.Serializable;
import java.util.*;

/**
 * 基础Service
 *
 * @create 2018-05-12 上午11:25
 **/
@Transactional(rollbackOn = Exception.class)
public abstract class BaseService<T extends BaseModel<I>, I extends Serializable> {

    @Autowired
    private ObjectMapper objectMapper;
    @PersistenceContext
    private EntityManager entityManager;

    @Autowired
    private BaseRepository<T, I> baseRepository;

    /**
     * 获取游离态实例
     *
     * @param id
     * @return
     */
    public T getById(I id) {
        T t = findStrictOne(id);
        if(Validator.isNull(t)){
            return null;
        }
        Session session = entityManager.unwrap(Session.class);
        session.evict(t);
        return t;
    }

    public T findOne(I id) {
        checkId(id);
        Optional<T> t = baseRepository.findById(id);
        if (!t.isPresent()) {
            return null;
        }
        return baseRepository.findById(id).get();
    }

    public T findStrictOne(I id) {
        T t = findOne(id);
        if (null == t) {
            throw new RuntimeException("【" + id + "】数据找不到");
        }
        return t;
    }

    public List<T> findAll() {
        return baseRepository.findAll();
    }

    public Page<T> page(Pageable pageable) {
        return baseRepository.findAll(pageable);
    }

    /**
     * 分页方法
     *
     * @param params 设置页码可传入 number（默认第一页） ,
     *               设置每页数据量可传入 size (默认10) ,
     *               设置排序 可传入 _sort_by_ 如 params.put("_sort_by_","id_desc") 表示按id倒序
     *               设置其他查询条件可传入 _filter_*_*  如 params.put("_filter_eq_id", 6) 表示按查询id=6的数据，更多查询条件使用请参考 FilterCondition 中的定义
     * @return
     */
    public Page<T> pagination(Map<String, Object> params) {
        Pageable pageable = this.getPageable(params);
        Specification<T> spec = new Specification<T>() {
            @Override
            public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                List<Predicate> list = new ArrayList<Predicate>();
                FilterCondition filterCondition = new FilterCondition(criteriaBuilder, root, params);
                list = filterCondition.getPredicateList();
                Predicate[] p = new Predicate[list.size()];
                return criteriaBuilder.and(list.toArray(p));
            }
        };
        Page<T> page = baseRepository.findAll(spec, pageable);
        Session session = entityManager.unwrap(Session.class);
        for (T t : page.getContent()) {
            session.evict(t);
        }
        return page;
    }

    /**
     * 根据条件处理params 中页码，每页大小，排序组装Pageable 返回给 分页方法（pagination）使用
     *
     * @param params
     * @return
     */
    public Pageable getPageable(Map<String, Object> params) {
        Integer number = 0;
        Integer size = 10;
        //处理页码
        if (params.containsKey("number") && params.get("number") != null && params.get("number").toString() != "") {
            Integer pno = Integer.valueOf(params.get("number").toString());
            number = pno == 0 ? 0 : pno - 1;
        }
        //处理每页大小
        if (params.containsKey("size") && params.get("size") != null && params.get("size").toString() != "") {
            size = Integer.valueOf(params.get("size").toString());
        }
        Pageable pageable = new PageRequest(number, size, Sort.Direction.DESC, "id");
        //处理排序  eg: _sort_by_: id_desc
        if (params.containsKey("_sort_by_") && params.get("_sort_by_") != null && params.get("_sort_by_").toString() != "") {
            String[] sortBy = params.get("_sort_by_").toString().split("_");
            if (sortBy.length == 2) {
                if (sortBy[1].toLowerCase().equals("asc")) {
                    pageable = new PageRequest(number, size, Sort.Direction.ASC, sortBy[0]);
                } else {
                    pageable = new PageRequest(number, size, Sort.Direction.DESC, sortBy[0]);
                }
            }
        }
        return pageable;
    }

    public Pageable getPageable(Map<String, Object> params, Sort sort) {
        Integer number = 0;
        Integer size = 10;
        //处理页码
        if (params.containsKey("number") && params.get("number") != null && params.get("number").toString() != "") {
            Integer pno = Integer.valueOf(params.get("number").toString());
            number = pno == 0 ? 0 : pno - 1;
        }
        //处理每页大小
        if (params.containsKey("size") && params.get("size") != null && params.get("size").toString() != "") {
            size = Integer.valueOf(params.get("size").toString());
        }
        Pageable pageable = new PageRequest(number, size, sort);
        //处理排序  eg: _sort_by_: id_desc
        if (params.containsKey("_sort_by_") && params.get("_sort_by_") != null && params.get("_sort_by_").toString() != "") {
            String[] sortBy = params.get("_sort_by_").toString().split("_");
            if (sortBy.length == 2) {
                if (sortBy[1].toLowerCase().equals("asc")) {
                    pageable = new PageRequest(number, size, Sort.Direction.ASC, sortBy[0]);
                } else {
                    pageable = new PageRequest(number, size, Sort.Direction.DESC, sortBy[0]);
                }
            }
        }
        return pageable;
    }

    public T save(T t) {
        if (null != t.getId() && exists(t.getId())) {
            throw new RuntimeException("【" + t.getId() + "】数据已存在");
        }
        t.setSaveTime(new Date());
        baseRepository.save(t);
        return t;
    }

    public T update(T t) {
        checkId(t.getId());
        if (!exists(t.getId())) {
            throw new RuntimeException("【" + t.getId() + "】数据找不到");
        }
        t.setUpdateTime(new Date());
        baseRepository.save(t);
        return t;
    }

    public T update(I id, Map<String, Object> map) {
        T old = findStrictOne(id);
        map.remove(id);
        T one = createDomainFromOldAndMap(old, map);
        doUpdateMap(old, one, map);
        one.setUpdateTime(new Date());
        return update(one);
    }

    public void delete(T t) {
        if (null == t) {
            return;
        }
        baseRepository.delete(t);
    }

    public void delete(I id) {
        baseRepository.deleteById(id);
    }

    public void deleteMore(Iterable<I> ids) {
        for (I id : ids) {
            delete(id);
        }
    }

    protected void doUpdateMap(T old, T one, Map<String, Object> map) {

    }

    protected boolean exists(I id) {
        return findOne(id) != null;
    }


    protected void checkId(I id) {
        if (id == null) {
            throw new RuntimeException("id参数不存在");
        }
    }

    private T createDomainFromOldAndMap(T oldDomain, Map<String, Object> map) {
        try {
            T newDomain = (T) oldDomain.getClass().newInstance();
            BeanUtils.copyProperties(oldDomain, newDomain);
            String json = objectMapper.writeValueAsString(map);
            return objectMapper.readerForUpdating(newDomain).readValue(json);
        } catch (IllegalArgumentException | IOException e) {
            throw new RuntimeException("无效参数");
        } catch (InstantiationException | IllegalAccessException e) {
            String message = "类[" + oldDomain.getClass().getCanonicalName() + "]找不到可访问无参构造器";
            throw new RuntimeException("内部代码错误");
        }
    }


}

